/*
 * Copyright (c) 2013, SRI International
 * All rights reserved.
 * Licensed under the The BSD 3-Clause License;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 * 
 * http://opensource.org/licenses/BSD-3-Clause
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the aic-web-praise nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.sri.ai.lpitools.executionengine.server.components.queryListener;

import java.util.Vector;

import java.util.logging.Logger;
import org.slf4j.Marker;

import com.google.gson.Gson;
import com.sri.ai.expresso.api.Expression;
import com.sri.ai.grinder.ui.ExpressionNode;
import com.sri.ai.lpitools.cobra.gwtutil.dataobjects.shared.TreeNode;
import com.sri.ai.lpitools.cobra.gwtutil.dataobjects.shared.TreeNode.NodeType;
import com.sri.ai.lpitools.executionengine.server.callback.CallBackClient_Interface;
import com.sri.ai.praise.lbp.LBPQueryEngine.TraceListener;

/**
 * A listener for collecting and sending the trace of a query to the web client.
 * @author dabraham
 *
 */
public class TraceListenerImpl implements TraceListener{

	private String myQuerysUUID;
	protected int currentIndentLevel = 0;
	protected String currentParentID = "0";
	protected int currentChildCount = 0;
	private CallBackClient_Interface client;
	private String clientID;
	protected Gson gson = new Gson();
	private boolean firstTime = true;
	private TreeNodeQueue queue;
	private static final Logger logger = Logger.getLogger(TraceListenerImpl.class.getName());	
	/**
	 * This method is called when a trace step is added.
	 */
	public void traceEvent(String queryUUID, int traceLevel, Long profileInfo, Marker marker,
			String msg, Object... args) {		
		if(!myQuerysUUID.equals(queryUUID)) return; //Filter all traces not belonging to this query.
		if(traceLevel<1) throw new IllegalArgumentException("Cannot have a nonpositive indent level."); //Tree cannot have levels higher than root.
		//Logic for indenting.
		while (traceLevel > currentIndentLevel) {
			if (!firstTime) {
				//First time is from root, doesn't require this step.
				//Create a node with label '>>', to replicate performance of original gui.
				TreeNode newNode = new TreeNode(currentParentID + "." + currentChildCount, ">>", TreeNode.TreeType.TRACE, NodeType.STRING, false);
				queue.EnqueueNode(gson.toJson(newNode), clientID);
				currentChildCount++;
			}
			else
			{
				//First time is from root, don't need any fancyness.
				firstTime = false;
				currentIndentLevel++;
			}
			if(currentChildCount == 0)
			{
				//Something went wrong in the AIC code, or we're not up to date with their code.
				if(currentParentID.equals("0")) break;
				currentChildCount = Integer.parseInt(currentParentID.substring(2).substring(0, currentParentID.substring(2).indexOf('.'))) + 1;
				currentParentID = "0";
			}
			else
			{
				//Move in a level.
				currentParentID = currentParentID + ("." + (currentChildCount - 1));
				currentChildCount=0;
			}
			currentIndentLevel++;
		}
		
		//Logic for "out-denting"
		StringBuilder sb = new StringBuilder(msg);
		while (traceLevel < currentIndentLevel) {
			try{
				//move out a level
				currentChildCount = Integer.parseInt(currentParentID.substring(currentParentID.lastIndexOf('.') + 1)) + 1;
				currentParentID = currentParentID.substring(0, currentParentID.lastIndexOf('.'));
			}
			catch(Exception ex)
			{
				//something went wrong. Set parent to root and move on.
				currentChildCount = Integer.parseInt(currentParentID.substring(2).substring(0, currentParentID.substring(2).indexOf('.'))) + 1;
				currentParentID = "0";
				currentIndentLevel = 0;
				break;
			}			
			currentIndentLevel--;
			if (traceLevel == currentIndentLevel) {
				// Suffix the profiler information to the message
				// if available.
				if (profileInfo != null) {
					sb.append(" [");
					// Convert nanoseconds to milliseconds
					sb.append(profileInfo / 1000000);
					sb.append("ms.]");
				}
			}
		}

		//Send the message to the web client
		if (msg != null && !msg.equals("")) {
			TreeNode newNode = new TreeNode(currentParentID + "." + currentChildCount, sb.toString(), TreeNode.TreeType.TRACE, NodeType.STRING, true);
			queue.EnqueueNode(gson.toJson(newNode), clientID);
			currentChildCount++;
		}

		//send the arguments
		if (args != null) {
			for (Object arg : args) {
				if(arg instanceof Expression)
				{
					//arg is an expression, compute and send it's children.
					ExpressionNode.NodeType type = (new ExpressionNode((Expression)arg, null, null).getType());
					NodeType convertedType;
					if(type != null)
					{
						convertedType = NodeType.valueOf(type.toString());
					}
					else
					{
						convertedType = null;
					}
					TreeNode newNode = new TreeNode(currentParentID + "." + currentChildCount, arg.toString(), TreeNode.TreeType.TRACE, convertedType, (new ExpressionNode((Expression)arg)).isLeaf());
					//send the node
					queue.EnqueueNode(gson.toJson(newNode), clientID);
					try{
						//recursively send computed subtrees.
						sendSubnodes(new ExpressionNode((Expression)arg), currentParentID + "." + currentChildCount);
					}
					catch(NullPointerException ex){
						//expression was bad, couldn't generate children. Move on.
						logger.info("Error generating subtrees, bad expression.");
					}
					currentChildCount++;
				}
				else
				{
					//arg is not an expression, send it as a string with no children.
					TreeNode newNode = new TreeNode(currentParentID + "." + currentChildCount, arg.toString(), TreeNode.TreeType.TRACE, null, true);
					queue.EnqueueNode(gson.toJson(newNode), clientID);
					currentChildCount++;
				}
			}
		}
	}

	/**
	 * Sends the nodes children, and their children.
	 * @param node the node to have its children trees recursively sent
	 * @param myID the node's ID.
	 */
	private void sendSubnodes(ExpressionNode node, String myID)
	{
		//compute children
		Vector<ExpressionNode> children = node.childrenAsVector();
		
		int myChildCount = 0;
		
		if(node.isLeaf()) return; //Don't call recursive method on a leaf, would be infinite.
		
		for(ExpressionNode child : children)
		{
			sendNodeAndSubnodes(child, myID + "." + myChildCount);
			myChildCount++;
		}
	}
	
	/**
	 * Similar to sendSubnodes, but sends expressionNode in addition to its children
	 * @param expressionNode the node to have itself and its children sent
	 * @param myID the node's ID.
	 */
	private void sendNodeAndSubnodes(ExpressionNode expressionNode, String myID) {
		//compute children
		Vector<ExpressionNode> children = expressionNode.childrenAsVector();
		
		int myChildCount = 0;
		
		ExpressionNode.NodeType type = expressionNode.getType();
		NodeType convertedType;
		if(type != null)
		{
			convertedType = NodeType.valueOf(type.toString());
		}
		else
		{
			convertedType = null;
		}
		
		TreeNode newNode = new TreeNode(myID, expressionNode.getToolTipText(), TreeNode.TreeType.TRACE, convertedType, expressionNode.isLeaf());
		queue.EnqueueNode(gson.toJson(newNode), clientID);
		
		if(expressionNode.isLeaf()) return; //Don't call recursive method on a leaf, would be infinite.
		
		for(ExpressionNode child : children)
		{
			sendNodeAndSubnodes(child, myID + "." + myChildCount);
			myChildCount++;
		}
	}
	
	//Getters and setters

	public CallBackClient_Interface getClient() {
		return client;
	}

	public void setClient(CallBackClient_Interface client) {
		this.client = client;
		if(queue != null)
		{
			queue.setClient(client);
		}
	}

	public String getClientID() {
		return clientID;
	}

	public void setClientID(String clientID) {
		this.clientID = clientID;
	}
	public void setQueue(TreeNodeQueue queue) {
		this.queue = queue;
		if(client != null)
		{
			queue.setClient(client);
		}
	}
	
	public String getMyQuerysUUID() {
		return myQuerysUUID;
	}

	public void setMyQuerysUUID(String myQuerysUUID) {
		this.myQuerysUUID = myQuerysUUID;
	}

}
